﻿
Imports std

    Friend Enum chartype
        unknown
        normal
        separator
        surround
    End Enum

Public Module _strsepar

    'this is a little quick than the one with surroundStr
    Public Function strsepar(Of T)(ByVal str As String, ByVal separators As String, ByVal result As T, _
                                       ByVal insert As insertDelegate(Of T, String), _
                                       ByVal ignoreEmptyEntity As Boolean) As T
        If Not isemptystring(str) Then
            assert(Not result Is Nothing, "result is nothing.")
            assert(Not insert Is Nothing, "insert is nothing.")

            If isemptystring(separators) Then
                insert(result, str)
            Else
                Dim i As Int32
                Dim last As Int32
                Dim len As Int32
                Dim nextChar As Int32
                Dim minNext As Int32
                Dim seplen As Int32

                last = 0
                len = strlen(str)
                seplen = strlen(separators)
                While last <= len
                    minNext = len
                    For i = 0 To seplen - 1
                        nextChar = str.IndexOf(separators(i), last)
                        If nextChar < minNext AndAlso nextChar <> npos Then
                            minNext = nextChar
                        End If
                    Next

                    If (Not ignoreEmptyEntity OrElse minNext - last > 0) Then
                        insert(result, strmid(str, last, minNext - last))
                    End If
                    last = minNext + 1
                End While
            End If
        End If

        Return result
    End Function

    Private Function chartypecheck(ByVal c As Char, _
                                   ByVal separators As String, ByVal surroundStr As String) As chartype
        If strindexof(separators, c, carecase:=True) <> npos Then
            Return chartype.separator
        ElseIf strindexof(surroundStr, c, carecase:=True) <> npos Then
            Return chartype.surround
        Else
            Return chartype.normal
        End If
    End Function

    Private Sub insertImpl(Of T)(ByVal str As String, ByVal last As Int64, ByVal i As Int64, _
                                 ByVal insert As insertDelegate(Of T, String), ByVal rst As T, _
                                 ByVal ignoreEmptyEntity As Boolean)
        If Not ignoreEmptyEntity OrElse last < i Then
            insert(rst, strmid(str, last, i - last))
        End If
    End Sub

    Public Function strsepar(Of T)(ByVal str As String, ByVal separators As String, ByRef result As T, _
                                   ByVal surroundStr As String, ByVal insert As insertDelegate(Of T, String), _
                                   Optional ByVal ignoreEmptyEntity As Boolean = False, _
                                   Optional ByVal ignoreSurroundChar As Boolean = True) As T
        If isemptystring(surroundStr) OrElse isemptystring(separators) Then
            strsepar(str, separators, result, insert, ignoreEmptyEntity)
        Else
            assert(Not result Is Nothing, "result is nothing.")
            assert(Not insert Is Nothing, "inserter is nothing.")

            Dim i As Int64
            Dim last As Int64
            Dim len As Int64
            Dim lastchartype As chartype
            Dim thischartype As chartype
            Dim surrounding As Boolean

            len = strlen(str)
            last = 0
            surrounding = False
            thischartype = chartype.unknown
            lastchartype = chartype.unknown

            'last does not contain surround char, and separator
            For i = 0 To len - 1
                lastchartype = thischartype
                thischartype = chartypecheck(str(i), separators, surroundStr)

                Select Case thischartype
                    Case chartype.surround
                        surrounding = Not surrounding
                        Select Case lastchartype
                            Case chartype.surround
                                If Not surrounding Then
                                    '""
                                    If Not ignoreEmptyEntity Then
                                        If ignoreSurroundChar Then
                                            'i-last-1
                                            insert(result, emptyString)
                                        Else
                                            'i-last+1
                                            insert(result, strmid(str, last, 2))
                                        End If
                                    End If
                                Else
                                    'ignore if this is a start of next surrounding string
                                End If
                            Case chartype.separator
                                'ignore if last char type is separator
                            Case chartype.normal
                                'a new start, so insert last to i
                                If ignoreSurroundChar Then
                                    insertImpl(str, last, i, insert, result, ignoreEmptyEntity)
                                Else
                                    insertImpl(str, last, i + 1, insert, result, ignoreEmptyEntity)
                                End If
                            Case chartype.unknown
                                'lastchartype can only be unknown when begin of the input string, so ignore
                        End Select

                        If surrounding AndAlso Not ignoreSurroundChar Then
                            last = i
                        Else
                            last = i + 1
                        End If
                    Case chartype.separator
                        If surrounding Then
                            'go on
                            thischartype = chartype.normal
                        Else
                            Select Case lastchartype
                                Case chartype.surround
                                    'ignore
                                Case chartype.separator, chartype.normal, chartype.unknown
                                    'if two separators, insert an empty if not ignoreEmptyEntity
                                    'first is a separator, same as separator
                                    insertImpl(str, last, i, insert, result, ignoreEmptyEntity)
                            End Select
                            last = i + 1
                        End If
                    Case chartype.normal
                        'go on
                    Case chartype.unknown
                        assert(False, "thischartype should not be unknown")
                End Select
            Next

            '1. the last char is surround char, and surrounding, add
            '2. last char is surround char, and not surrounding, not add
            '3. last char is not surround char, add
            If thischartype <> chartype.surround OrElse surrounding Then
                insertImpl(str, last, i, insert, result, ignoreEmptyEntity)
            End If
        End If

        Return result
    End Function

    Public Function strsepar(ByVal str As String, ByVal seperators As String, ByRef result As vector(Of String), _
                             ByVal surroundStr As String, Optional ByVal ignoreEmptyEntity As Boolean = False, _
                             Optional ByVal ignoreSurroundChar As Boolean = True) As vector(Of String)
        If result Is Nothing Then
            result = New vector(Of String)()
        Else
            result.clear()
        End If

        Return strsepar(str, seperators, result, surroundStr, AddressOf vectorInsert, _
                        ignoreEmptyEntity, ignoreSurroundChar)
    End Function

    Public Function strsepar(ByVal str As String, ByVal seperators As String, ByRef result As vector(Of String), _
                             Optional ByVal ignoreEmptyEntity As Boolean = False) As vector(Of String)
        If result Is Nothing Then
            result = New vector(Of String)()
        Else
            result.clear()
        End If

        Return strsepar(str, seperators, result, AddressOf vectorInsert, ignoreEmptyEntity)
    End Function

    Public Function strsepar(ByVal str As String, ByVal seperators As String, ByRef result As [set](Of String), _
                             Optional ByVal ignoreEmptyEntity As Boolean = False) As [set](Of String)
        If result Is Nothing Then
            result = New [set](Of String)
        Else
            result.clear()
        End If

        Return strsepar(str, seperators, result, AddressOf setInsert, ignoreEmptyEntity)
    End Function

    Public Function strsepar(ByVal str As String, ByVal seperators As String, ByRef result As list(Of String), _
                             Optional ByVal ignoreEmptyEntity As Boolean = False) As list(Of String)
        If result Is Nothing Then
            result = New list(Of String)()
        Else
            result.clear()
        End If

        Return strsepar(str, seperators, result, AddressOf listInsert, ignoreEmptyEntity)
    End Function

    Public Function strsepar2(Of T)(ByVal str As String, ByVal separator As String, ByRef result As T, _
                                    ByVal insert As insertDelegate(Of T, String), _
                                    Optional ByVal ignoreEmptyEntity As Boolean = False) As T
        If Not isemptystring(str) Then
            assert(Not result Is Nothing)
            assert(Not insert Is Nothing)

            If isemptystring(separator) Then
                insert(result, str)
            Else
                Dim index As Int64
                index = strindexof(str, separator)
                While index <> npos
                    If Not ignoreEmptyEntity OrElse index > 0 Then
                        Dim s As String = Nothing
                        s = strleft(str, index)
                        insert(result, s)
                    End If
                    str = strmid(str, index + strlen(separator))
                    index = strindexof(str, separator)
                End While

                If Not ignoreEmptyEntity OrElse strlen(str) > 0 Then
                    insert(result, str)
                End If
            End If
        End If
        Return result
    End Function

    Public Function strsepar2(ByVal str As String, ByVal seperator As String, ByRef result As vector(Of String), _
                              Optional ByVal ignoreEmptyEntity As Boolean = False) As vector(Of String)
        If result Is Nothing Then
            result = New vector(Of String)()
        Else
            result.clear()
        End If

        Return strsepar2(str, seperator, result, AddressOf vectorInsert, ignoreEmptyEntity)
    End Function

    Public Function strsepar2(ByVal str As String, ByVal separator As String, ByRef result As [set](Of String),
                              Optional ByVal ignoreEmptyEntity As Boolean = False) As [set](Of String)
        If result Is Nothing Then
            result = New [set](Of String)()
        Else
            result.clear()
        End If

        Return strsepar2(str, separator, result, AddressOf setInsert, ignoreEmptyEntity)
    End Function

    Private Sub unittestOutput(ByVal rst As vector(Of String))
        Console.WriteLine("total count " + Convert.ToString(rst.size()))
        Dim i As Int64
        For i = 0 To rst.size() - 1
            Console.WriteLine(rst(i) + " len " + Convert.ToString(strlen(rst(i))))
        Next
    End Sub

    Public Sub unittest()
        Console.WriteLine("start strsepar unittest, input string, separator, surround char.")
        Console.Write("input string ")
        Dim s As String = Nothing
        s = Console.ReadLine()
        While Not s Is Nothing
            Console.Write("input separator ")
            Dim sep As String = Nothing
            sep = Console.ReadLine()
            Console.Write("input surround char ")
            Dim sur As String = Nothing
            sur = Console.ReadLine()

            Dim rst As vector(Of String) = Nothing
            strsepar(s, sep, rst, sur, False, False)
            If Not rst Is Nothing Then
                Console.WriteLine("output when not ignoreEmptyEntities, and not ignoreSurroundChar")
                unittestOutput(rst)
            End If

            strsepar(s, sep, rst, sur, True, False)
            If Not rst Is Nothing Then
                Console.WriteLine("output when ignoreEmptyEntities, and not ignoreSurroundChar")
                unittestOutput(rst)
            End If

            strsepar(s, sep, rst, sur, False, True)
            If Not rst Is Nothing Then
                Console.WriteLine("output when not ignoreEmptyEntities, and ignoreSurroundChar")
                unittestOutput(rst)
            End If

            strsepar(s, sep, rst, sur, True, True)
            If Not rst Is Nothing Then
                Console.WriteLine("output when ignoreEmptyEntities, and ignoreSurroundChar")
                unittestOutput(rst)
            End If

            Console.Write("input string ")
            s = Console.ReadLine()
        End While
    End Sub
End Module
